/////////////////////////////////////////////////////////////////////////////
// Original code from libhdfs3. Copyright (c) 2013 - 2014, Pivotal Inc.
// All rights reserved. Author: Zhanwei Wang
/////////////////////////////////////////////////////////////////////////////
//  Modifications by Kumo Inc.
// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//




#include <kmhdfs/common/exception.h>
#include <kmhdfs/common/exception_internal.h>
#include <cstring>
#include <cassert>
#include <sstream>

namespace Hdfs {
    std::function<bool(void)> ChecnOperationCanceledCallback;

    namespace Internal {
        bool CheckOperationCanceled() {
            if (ChecnOperationCanceledCallback && ChecnOperationCanceledCallback()) {
                THROW(HdfsCanceled, "Operation has been canceled by the user.");
            }

            return false;
        }

        static thread_local char message[64];

        const char *GetSystemErrorInfo(int eno) {
            char buffer[64], *pbuffer;
            pbuffer = buffer;
#ifdef STRERROR_R_RETURN_INT
            strerror_r(eno, buffer, sizeof(buffer));
#else
            pbuffer = strerror_r(eno, buffer, sizeof(buffer));
#endif
            snprintf(message, sizeof(message), "(errno: %d) %s", eno, pbuffer);
            return message;
        }

        static void GetExceptionDetailInternal(const Hdfs::HdfsException &e,
                                               std::stringstream &ss, bool topLevel);

        static void GetExceptionDetailInternal(const std::exception &e,
                                               std::stringstream &ss, bool topLevel) {
            try {
                if (!topLevel) {
                    ss << "Caused by\n";
                }

                ss << e.what();
            } catch (const std::bad_alloc &e) {
                return;
            }

            try {
                Hdfs::rethrow_if_nested(e);
            } catch (const Hdfs::HdfsException &nested) {
                GetExceptionDetailInternal(nested, ss, false);
            } catch (const std::exception &nested) {
                GetExceptionDetailInternal(nested, ss, false);
            }
        }

        static void GetExceptionDetailInternal(const Hdfs::HdfsException &e,
                                               std::stringstream &ss, bool topLevel) {
            try {
                if (!topLevel) {
                    ss << "Caused by\n";
                }

                ss << e.msg();
            } catch (const std::bad_alloc &e) {
                return;
            }

            try {
                Hdfs::rethrow_if_nested(e);
            } catch (const Hdfs::HdfsException &nested) {
                GetExceptionDetailInternal(nested, ss, false);
            } catch (const std::exception &nested) {
                GetExceptionDetailInternal(nested, ss, false);
            }
        }

        const char *GetExceptionDetail(const Hdfs::HdfsException &e,
                                       std::string &buffer) {
            try {
                std::stringstream ss;
                ss.imbue(std::locale::classic());
                GetExceptionDetailInternal(e, ss, true);
                buffer = ss.str();
            } catch (const std::bad_alloc &e) {
                return "Out of memory";
            }

            return buffer.c_str();
        }

        const char *GetExceptionDetail(const exception_ptr e, std::string &buffer) {
            std::stringstream ss;
            ss.imbue(std::locale::classic());

            try {
                Hdfs::rethrow_exception(e);
            } catch (const Hdfs::HdfsException &nested) {
                GetExceptionDetailInternal(nested, ss, true);
            } catch (const std::exception &nested) {
                GetExceptionDetailInternal(nested, ss, true);
            }

            try {
                buffer = ss.str();
            } catch (const std::bad_alloc &e) {
                return "Out of memory";
            }

            return buffer.c_str();
        }

        static void GetExceptionMessage(const std::exception &e,
                                        std::stringstream &ss, int recursive) {
            try {
                for (int i = 0; i < recursive; ++i) {
                    ss << '\t';
                }

                if (recursive > 0) {
                    ss << "Caused by: ";
                }

                ss << e.what();
            } catch (const std::bad_alloc &e) {
                return;
            }

            try {
                Hdfs::rethrow_if_nested(e);
            } catch (const std::exception &nested) {
                GetExceptionMessage(nested, ss, recursive + 1);
            }
        }

        const char *GetExceptionMessage(const exception_ptr e, std::string &buffer) {
            std::stringstream ss;
            ss.imbue(std::locale::classic());

            try {
                Hdfs::rethrow_exception(e);
            } catch (const std::bad_alloc &e) {
                return "Out of memory";
            } catch (const std::exception &e) {
                GetExceptionMessage(e, ss, 0);
            }

            try {
                buffer = ss.str();
            } catch (const std::bad_alloc &e) {
                return "Out of memory";
            }

            return buffer.c_str();
        }
    }
}
